#==================================================
#	Script Editor Getter
#==================================================
#  获取 ScriptEditor 代码编辑部分的节点
#==================================================
# * 创建后需要先 set_editor_interface() 设置编辑器接口
#   例，在一个插件中的使用方式：
#
# 	extends EditorPlugin
#	
#	const ScriptEditorGetter = preload( ScriptEditorGetter这个脚本所在的路径 )
#	
#	var seg = ScriptEditorGetter.new()
#
#	func _enter_tree():
#		seg.set_editor_interface(get_editor_interface())
#
# * 主要用于编辑代码的插件
#==================================================
# [其他未公开的方法]
#	get_editor_interface().get_script_editor()._reload_scripts()
#	get_editor_interface().get_script_editor()._resave_scripts()
#	get_editor_interface().get_script_editor()._script_created()
#	get_editor_interface().get_script_editor()._close_docs_tab()
#	get_editor_interface().get_script_editor()._res_saved_callback()
#	get_editor_interface().get_script_editor()._menu_option()
#	get_editor_interface().get_script_editor()._tab_changed()
#==================================================
# @datetime: 2021-9-1 14:08:17
#==================================================

extends Reference



var _editor_interface : EditorInterface setget set_editor_interface, get_editor_interface

var _file = File.new()
var _tab_container : TabContainer
var _item_list : ItemList
var _script_editor : ScriptEditor setget , get_script_editor



#==================================================
#   Set/Get
#==================================================
##  设置编辑器接口（需要设置这个属性才能使用）
func set_editor_interface(value: EditorInterface) -> void:
	_editor_interface = value

func get_editor_interface() -> EditorInterface:
	return _editor_interface



#==================================================
#   获取节点
#==================================================
##  获取脚本编辑器总节点
## @return  返回 ScriptEditor 节点
func get_script_editor() -> ScriptEditor:
	if _script_editor == null:
		_script_editor = get_editor_interface().get_script_editor()
	return _script_editor


##  获取脚本的文本编辑器
## @return  返回 ScriptTextEditor 节点
## （它是个组合节点，需要进一步获取其中的节点）
func get_current_script_text_editor() -> Control:
	return get_tab_container().get_child(get_tab_container().current_tab) as Control


##  返回脚本编辑框父节点 Tab Container
## @return  返回 TabContainer 节点，里面包含 ScriptEditor 列表
func get_tab_container() -> TabContainer:
	if _tab_container == null:
		_tab_container = (
			get_editor_interface() \
				.get_script_editor() \
				.get_child(0) \
				.get_child(1) \
				.get_child(1)
		) as TabContainer
	return _tab_container


##  返回脚本编辑框左侧项目列表
## @return  返回 ItemList
func get_item_list() -> ItemList:
	if _item_list == null:
		_item_list = (
			get_tab_container() \
				.get_parent() \
				.get_child(0) \
				.get_child(0) \
				.get_child(1)
		) as ItemList
	return _item_list


## 返回当前代码编辑框
func get_current_code_textedit() -> TextEdit:
	return _get_code_editor(
			get_tab_container().get_current_tab_control()
		) 


##  获取当前脚本
## @return  返回当前脚本
func get_current_script() -> Script:
	return get_editor_interface() \
			.get_script_editor() \
			.get_current_script()


##  返回脚本编辑框下的代码编辑框 
## @script_editor  脚本编辑框
## @return  返回代码编辑框 TextEdit
## （脚本编辑框和代码编辑框的区别：
##  * 脚本编辑框是个各种节点组合成的复合节点
##  * 代码编辑框是个文本编辑框，可直接输入操控代码
## ）
func _get_code_editor(script_editor: Control) -> TextEdit:
	var code_editor = script_editor
	# 代码文本编辑器在第3级子节点
	for i in 3:
		if code_editor == null:
			return null
		code_editor = code_editor.get_child(0)
	return code_editor


##  返回脚本编辑框列表
func get_script_editor_list() -> Array:
	var list := []
	for child in get_tab_container().get_children():
		if child.get_class() == "ScriptTextEditor":
			list.push_back(child)
	return list


##  返回脚本编辑器菜单的父节点
func get_script_editor_menu_parent() -> HBoxContainer:
	# 获取编辑器视图
	var v_editor = get_editor_interface().get_editor_viewport()
	# 获取脚本编辑器菜单列表
	return v_editor \
			.get_child(2) \
			.get_child(0) \
			.get_child(0)

##  获取脚本编辑框菜单列表
## @return  返回菜单按钮列表
func get_script_editor_menu_list() -> Array:
	var list := []
	for child in get_script_editor_menu_parent().get_children():
		if child is MenuButton:
			list.push_back(child)
	return list


##  获取 File 菜单按钮
## @return  返回菜单按钮
func get_file_menu() -> MenuButton:
	var p = get_script_editor_menu_parent()
	for child in p.get_children():
		if (
			child is MenuButton
			&& child.text == "File"
		):
			return child as MenuButton
	return null



#==================================================
#   获取脚本相关数据
#==================================================
##  获取已打开的脚本路径列表
## @return  返回脚本路径列表
func get_open_script_path_list() -> Array:
	var list := []
	var data_list := get_script_data()
	for data in data_list:
		list.push_back(data["script_path"])
	return list


##  获取脚本数据
## @return  返回代码编辑器左侧脚本列表信息
##    数据中的 key 包括：
##   * item_name  item 名称
##   * script_path  脚本路径、
##   * tab_idx  脚本编辑框的 tab 索引
func get_script_data() -> Array:
	var item_list = get_item_list()
	var data_list = []
	var idx = 0
	for i in range(item_list.items.size() / 3):
		idx = 3 * i
		var path = item_list.get_item_tooltip(i)
		if path.right(path.length() - 3) == ".gd":
			data_list.push_back({
				item_name = item_list.items[idx],
				script_path = path,
				tab_idx = item_list.get_item_metadata(i),
			})
	return data_list


##  获取指定脚本的脚本编辑框的 tab 值
## @script_path  脚本路径
## @return  返回 tab 值，如果没有则返回 -1
func _get_script_tab(script_path: String) -> int:
	var data_list := get_script_data()
	for data in data_list:
		if data["script_path"] == script_path:
			return data['tab_idx']
	return -1



#==================================================
#   执行功能动作
#==================================================
##  添加脚本编辑器菜单 
## @menu_button  菜单按钮
func add_script_editor_menu(menu_button: MenuButton) -> void:
	var parent = get_script_editor_menu_parent()
	var last_menu_idx : int = -1
	for idx in range(parent.get_children().size() -1, -1, -1):
		var child = parent.get_child(idx)
		if child is MenuButton && not child is ToolButton:
			last_menu_idx = child.get_index()
			break
	
	# 添加 MenuButton 节点
	parent.add_child(menu_button, true)
	parent.move_child(menu_button, last_menu_idx + 1)


##  切换到指定脚本的 tab_idx 页
## @script_path  脚本的路径
func switch_tab(script_path: String) -> void:
	var tab_idx = _get_script_tab(script_path)
	if tab_idx > -1:
		get_tab_container().current_tab = tab_idx
	else:
		# 切换失败没有脚本 就没有这个 tab
		push_warning("[Fail] Script does not exist.")


##  关闭脚本
## @script  脚本或带有路径的脚本
func close_script(script) -> void:
	var script_path = ""
	if script is String:
		script_path = script
	elif script is Script:
		script_path = script.resource_path
	else:
		printerr("[参数错误] 参数值必须是 <String/Script> 类型！")
		return
	
	var tab_idx = _get_script_tab(script_path)
	if tab_idx > -1:
		close_script_by_tab(tab_idx)
	else:
		# 关闭失败没有这个 tab
		push_warning("[Fail] Script does not exist.")


##  关闭脚本（通过 idx 关闭）
## @tab_idx  tab索引值
## @ignore_save  忽略脚本是否保存
func close_script_by_tab(
	tab_idx: int, 
	ignore_save : bool = false
) -> void:
	var item_list = get_item_list()
	var tab_container = get_tab_container()
	var data_list = get_script_data()
	if data_list.size() <= tab_idx:
		print("tab_idx 超出总数量")
		return
	
	# item 名称
	var item := data_list[tab_idx]['item_name'] as String
	var item_script_path := data_list[tab_idx]["script_path"] as String
	
	# 如果脚本是存在的，且代码编辑框中进行了编辑但未保存时
	if (not ignore_save
		&& _file.file_exists(item_script_path)
		&& item.right(item.length() - 3) == "(*)"
	):
		print("[关闭脚本失败] 脚本还未保存!")
		push_warning("[Fail] The script has not been saved!")
		return
	
	# 移除
	close_tab(tab_idx)


##  关闭所有无效的脚本
## （有时脚本已删除，但脚本编辑框左上侧代码列表还存在时调用这个方法）
func close_all_invalid_script() -> void:
	var data_list = get_script_data()
	var item_text : String
	var item_script_path : String
	for idx in range(data_list.size()):
		item_text = data_list[idx]["item_name"] as String
		item_script_path = data_list[idx]["script_path"] as String
		if (
			item_text == ""
			|| not _file.file_exists(item_script_path)
		):
			close_tab(idx)


##  关闭 Tab 页
## @tab_idx  索引
func close_tab(tab_idx: int) -> void:
	var selected_item = get_item_list().get_selected_items()
	get_item_list().remove_item(tab_idx)
	var script_editor = get_tab_container().get_child(tab_idx)
	# 每个打开的脚本都会附带有一个 HBoxContainer 的搜索、编辑菜单
	var script_menu_name = "HBoxContainer"
	var idx = int(script_editor.name)
	if idx > 0:
		script_menu_name += str(idx)
	# 移除对应的脚本菜单
	for child in get_script_editor_menu_parent().get_children():
		if (
			child is HBoxContainer
			&& child.name == script_menu_name
		):
			child.queue_free()
			break
	script_editor.queue_free()
	
	if selected_item.size() > 0:
		var current_select = selected_item[0]
		if current_select >= tab_idx:
			switch_tab(current_select - 1)


##  脚本是否已保存
## @script_path  脚本路径
## @return  返回是否保存了
func script_is_saved(script_path: String) -> bool:
	var data_list = get_script_data()
	var idx = 0
	for data in data_list:
		if data["script_path"] == script_path:
			var item := data_list[idx]['item_name'] as String
			# item末尾字符串不是 (*) 则代表已保存
			return item.right(item.length() - 3) != "(*)"
		idx += 1
	return true


##  重新加载指定脚本的代码编辑框中的代码
## 需要重新保存
## @script_path  脚本路径
func reload_code_editor_code(script_path: String) -> void:
	# 不存在文件，则不执行
	if not _file.file_exists(script_path):
		printerr("[重新加载失败] 指定脚本不存在")
		return
	
	# 获取代码编辑框
	var tab_container = get_tab_container()
	var tab_idx = _get_script_tab(script_path)
	if tab_idx == -1:
		return
	var script_editor = tab_container.get_tab_control(tab_idx)
	var code_editor := _get_code_editor(script_editor) as TextEdit
	if code_editor == null:
		print("[重新加载失败] 没有 %s 文件的 CodeEditor" % script_path)
		return
	
	# 打开文件
	_file.open(script_path, File.READ)
	var code = _file.get_as_text()
	_file.close()
	
	# 更新代码编辑框的内容
	if code != code_editor.text:
		code_editor.text = code


##  重新加载所有脚本
## (尽量避免使用这个方法，未保存的代码将会丢失！)
func reload_all_script() -> void:
	print("[注意] 使用此方法将造成：未保存的代码将会丢失！")
	get_editor_interface().get_script_editor()._reload_scripts()


##  保存所有脚本文件 
func save_all_script() -> void:
	# 菜单枚举值，FILE_SAVE_ALL 为 7	# 全部保存
	var FILE_SAVE_ALL : int = 7
	get_editor_interface().get_script_editor()._menu_option(FILE_SAVE_ALL)
	
	# 其他可能会用到的枚举值
	# 全部枚举详见：https://github.com/godotengine/godot/blob/master/editor/plugins/script_editor_plugin.h
#	var FILE_SAVE = 5	# 保存
#	var FILE_SAVE_AS = 6	# 另存为
#	var CLOSE_DOCS = 11	# 关闭全部打开的帮助文档
#	var CLOSE_ALL = 12	# 关闭全部打开的脚本和帮助文档
#	var SHOW_IN_FILE_SYSTEM = 15	# 在 FileSystem 中定位到这个文件



